Completed
Push — master ( 44d532...dfbaad )
by Esaú
01:39
created

hierarchy-helper.js ➔ testThird   A

Complexity

Conditions 1
Paths 64

Size

Total Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 64
dl 0
loc 66
rs 9.3191
nop 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A hierarchy-helper.js ➔ ... ➔ ??? 0 8 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
// spec/helpers/hierarchy-helper.js
2
"use strict";
3
4
// :: DEPENDENCIES
5
6
const path = require("path");
7
const root = path.dirname(path.dirname(__dirname));
8
9
module.exports = (hierarchy) => {
10
    // check parameters
11
    if (!Array.isArray(hierarchy)) {
12
        throw new Error("hierarchy must be an array");
13
    }
14
15
    // load dependencies
16
    let deps      = hierarchy.map(value => require(path.join(root, value + ".js")));
17
    let klassName = hierarchy.pop();
18
    let Klass     = deps.pop();
19
    const first   = (hierarchy.length === 0);
20
    const third   = (hierarchy.length >= 3);
21
22
    // :: TESTING
23
24
    // test the last class in the hierarchy tree
25
    describe(klassName, () => {
26
27
        // :: INHERITED PROTOTYPE
28
29
        // all inherit from Object
30
        it("should inherit from 'Object'", () => {
31
            expect(new Klass()).toEqual(jasmine.any(Object));
32
        });
33
34
        // check the hierarchy tree
35
        for (let i = 0; i < hierarchy.length; i += 1) {
36
            it("should inherit from '" + hierarchy[i] + "'", () => {
37
                expect(new Klass()).toEqual(jasmine.any(deps[i]));
38
            });
39
        }
40
41
        // check inherited properties
42
        if (!first) {
43
            it("should have a prototype property named 'name'", () => {
44
                expect(Klass.prototype).toHaveString("name");
45
            });
46
47
            it("should have a prototype property named 'message'", () => {
48
                expect(Klass.prototype).toHaveString("message");
49
            });
50
51
            it("should have a prototype property named 'code'", () => {
52
                expect(Klass.prototype).toHaveMember("code");
53
            });
54
        }
55
56
        // check Object methods
57
        it("should have a prototype method named 'toString()'", () => {
58
            expect(Klass.prototype).toHaveMethod("toString");
59
        });
60
61
        // check inherited methods
62
        if (!first) {
63
            it("should have a prototype method named 'native()'", () => {
64
                expect(Klass.prototype).toHaveMethod("native");
65
            });
66
        }
67
68
        // :: EXTENDED PROTOTYPE
69
70
        // check extended properties
71
        if (first) {
72
            it("should have a prototype property named 'name'", () => {
73
                expect(Klass.prototype).toHaveString("name");
74
            });
75
76
            it("should have a prototype property named 'message'", () => {
77
                expect(Klass.prototype).toHaveString("message");
78
            });
79
80
            it("should have a prototype property named 'code'", () => {
81
                expect(Klass.prototype).toHaveMember("code");
82
            });
83
84
            it("should have a prototype method named 'native()'", () => {
85
                expect(Klass.prototype).toHaveMethod("native");
86
            });
87
        }
88
89
        // :: PROTOTYPE VALUES
90
91
        it("should have the 'class' name in the prototype property named 'name'", () => {
92
            expect(Klass.prototype.name).toEqual(klassName);
93
        });
94
95
        it("should have a dummy default value as message", () => {
96
            expect(Klass.prototype.message).toEqual("thrown");
97
        });
98
99
        it("should have a null default value as code", () => {
100
            expect(Klass.prototype.code).toBeNull();
101
        });
102
103
        // :: CONSTRUCTOR
104
105
        it("should instantiate without parameters", () => {
106
            instanceNoParameters(Klass, testNoErrors, third);
107
            testNoErrors(() => new Klass());
108
        });
109
110
        it("should instantiate with parameters", () => {
111
            instanceParameters(Klass, testNoErrors, testNoErrors, testNoErrors, third);
112
        });
113
114
        // use the tests according to the hierarchy level
115
        if (third) {
116
            testThird(Klass);
117
        } else {
118
            test(Klass);
119
        }
120
121
    });
122
123
};
124
125
// Tests that a function doesn't throw any Error
126
function testNoErrors(fn) {
127
    expect(fn).not.toThrowError("parameter 'name' must be a 'string'");
128
    expect(fn).not.toThrowError("parameter 'message' must be a 'string'");
129
    expect(fn).not.toThrowError("parameter 'code' must be a 'number'");
130
}
131
132
// Tests instantiation of a class without parameters (undefined, null or none).
133
function instanceNoParameters(Klass, fn, third) {
134
    let arg1, arg2, arg3, test;
135
    test = (() => new Klass(arg1, arg2, arg3));
136
    for (let i = 0; i < 2; i += 1) {
137
        arg1 = (i % 2 === 0 ? undefined : null);
138
        for (let j = 0; j < 2; j += 1) {
139
            arg2 = (j % 2 === 0 ? undefined : null);
140
            if (third) {
141
                fn(test);
142
            } else {
143
                for (let e = 0; e < 2; e += 1) {
144
                    arg3 = (e % 2 === 0 ? undefined : null);
145
                    fn(test);
146
                }
147
            }
148
        }
149
    }
150
}
151
152
// Tests instantiation of a class with parameters.
153
function instanceParameters(Klass, fn1, fn2, fn3, third) {
154
    let arg1, arg2, arg3, test3, args1, args2, args3;
155
    const test1 = (() => new Klass(arg1));
156
    const test2 = (() => new Klass(arg1, arg2));
157
    if (third) {
158
        test3 = (() => null);
159
        args1 = [undefined, null, Klass.prototype.message];
160
        args2 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
161
        args3 = [];
162
    } else {
163
        test3 = (() => new Klass(arg1, arg2, arg3));
164
        args1 = [undefined, null, Klass.prototype.name];
165
        args2 = [undefined, null, Klass.prototype.message];
166
        args3 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
167
    }
168
    for (let i = 0; i < args1.length; i += 1) {
169
        arg1 = args1[i];
170
        fn1(test1);
171
        for (let j = 0; j < args2.length; j += 1) {
172
            arg2 = args2[j];
173
            fn2(test2);
174
            for (let e = 0; !third && e < args3.length; e += 1) {
175
                arg3 = args3[e];
176
                fn3(test3);
177
            }
178
        }
179
    }
180
}
181
182
// Loops for each parameter of the Klass constructor.
183
// If the iteration is even, the parameter is defined.
184
// If the iteration is odd, the parameter is null.
185
function instanceDefinedOrNull(Klass, name, message, code, fn1, fn2, fn3, third) {
186
    for (let i = 0; i < 2; i += 1) {
187
        const even1   = (i % 2 === 0);
188
        const arg1    = (even1 ? (third ? message : name) : null);
189
        const source1 = new Klass(arg1);
190
        fn1(source1, even1);
191
        for (let j = 0; j < 2; j += 1) {
192
            const even2   = (j % 2 === 0);
193
            const arg2    = (even2 ? (third ? code : message) : null);
194
            const source2 = new Klass(arg1, arg2);
195
            fn2(source2, even1, even2);
196
            for (let e = 0; !third && e < 2; e += 1) {
197
                const even3   = (e % 2 === 0);
198
                const arg3    = (even3 ? code : null);
199
                const source3 = new Klass(arg1, arg2, arg3);
200
                fn3(source3, even1, even2, even3);
201
            }
202
        }
203
    }
204
}
205
206
// Loops for each parameter of the Klass constructor using wrong types to test Error throwing.
207
function instanceThrowErrors(Klass, fn1, fn2, fn3, third) {
208
    let arg1, arg2, arg3, test33, test32, test31, test21, test22, test11, len1, len2, len3;
209
    const noStr = [{}, true, false, 42, 3.1416, -42, -3.1416, () => null];
210
    const noNmb = [{}, true, false, '', "qwerty", () => null];
211
    len1        = noStr.length;
212
    if (third) {
213
        len2   = noNmb.length;
214
        len3   = 0;
215
        test33 = test32 = test31 = (() => null);
216
    } else {
217
        len2   = len1;
218
        len3   = noNmb.length;
219
        test33 = (() => new Klass(arg1, arg2, arg3));
220
        test32 = (() => new Klass(null, arg2, arg3));
221
        test31 = (() => new Klass(null, null, arg3));
222
    }
223
    test22 = (() => new Klass(arg1, arg2));
224
    test21 = (() => new Klass(null, arg2));
225
    test11 = (() => new Klass(arg1));
226
    if (typeof Symbol === "function") {
227
        noStr.push(Symbol("symbol"));
228
        noNmb.push(Symbol("symbol"));
229
    }
230
    for (let i = 0; i < len1; i += 1) {
231
        arg1 = noStr[i];
232
        fn1(test11);
233
        for (let j = 0; j < len2; j += 1) {
234
            arg2 = (third ? noNmb[j] : noStr[j]);
235
            fn2(test21, test22);
236
            for (let e = 0; !third && e < len3; e += 1) {
237
                arg3 = noNmb[e];
238
                fn3(test31, test32, test33);
239
            }
240
        }
241
    }
242
}
243
244
// Tests classes that are a third level subclass, meaning that they require 2 arguments (message and code).
245
function testThird(Klass) {
246
247
    // :: CONSTRUCTOR
248
249
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
250
        instanceThrowErrors(Klass, (test11) => {
251
            expect(test11).toThrowError("parameter 'message' must be a 'string'");
252
        }, (test21, test22) => {
253
            expect(test21).toThrowError("parameter 'code' must be a 'number'");
254
            expect(test22).toThrowError("parameter 'message' must be a 'string'");
255
        }, null, true);
256
    });
257
258
    // :: MEMBER PROPERTIES
259
260
    const message = "asdf";
261
    const code    = Math.round(Math.random() * 0xFFFFFFFF);
262
263
    it("should have all correct properties once instantiated", () => {
264
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
265
            if (even) {
266
                expect(instance.name).toEqual(Klass.prototype.name);
267
                expect(instance.message).toEqual(message);
268
            } else {
269
                expect(instance.name).toEqual(Klass.prototype.name);
270
                expect(instance.message).toEqual(Klass.prototype.message);
271
            }
272
            expect(instance.code).toBeNull();
273
        }, (instance, even1, even2) => {
274
            expect(instance.name).toEqual(Klass.prototype.name);
275
            expect(instance.message).toEqual(even1 ? message : Klass.prototype.message);
276
            expect(instance.code).toEqual(even2 ? code : null);
277
        }, null, true);
278
    });
279
280
    // :: MEMBER METHODS
281
282
    it("#toString()", () => {
283
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
284
            let exp;
285
            if (even) {
286
                exp = Klass.prototype.name + ": " + message + '.';
287
            } else {
288
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
289
            }
290
            expect(instance.toString()).toEqual(exp);
291
        }, (instance, even1, even2) => {
292
            let exp;
293
            exp  = Klass.prototype.name;
294
            exp += (even2 ? " (0x" + code.toString(16) + "):" : ':' ) + ' ';
295
            exp += (even1 ? message : Klass.prototype.message) + '.';
296
            expect(instance.toString()).toEqual(exp);
297
        }, null, true);
298
    });
299
300
    it("#native()", () => {
301
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
302
            const exp = (even ? message : Klass.prototype.message);
303
            expect(instance.native()).toEqual(new Error(exp));
304
        }, (instance, even1) => {
305
            const exp = (even1 ? message : Klass.prototype.message);
306
            expect(instance.native()).toEqual(new Error(exp));
307
        }, null, true);
308
    });
309
310
}
311
312
// Tests classes that require 3 arguments (name, message and code).
313
function test(Klass) {
314
315
    // :: CONSTRUCTOR
316
317
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
318
        instanceThrowErrors(Klass, (test11) => {
319
            expect(test11).toThrowError("parameter 'name' must be a 'string'");
320
        }, (test21, test22) => {
321
            expect(test22).toThrowError("parameter 'name' must be a 'string'");
322
            expect(test21).toThrowError("parameter 'message' must be a 'string'");
323
        }, (test31, test32, test33) => {
324
            expect(test33).toThrowError("parameter 'name' must be a 'string'");
325
            expect(test32).toThrowError("parameter 'message' must be a 'string'");
326
            expect(test31).toThrowError("parameter 'code' must be a 'number'");
327
        }, false);
328
    });
329
330
    // :: MEMBER PROPERTIES
331
332
    const name    = "qwerty";
333
    const message = "asdf";
334
    const code    = Math.round(Math.random() * 0xFFFFFFFF);
335
336
    it("should have all correct properties once instantiated", () => {
337
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
338
            if (even) {
339
                expect(instance.name).toEqual(name);
340
                expect(instance.message).toEqual(Klass.prototype.message);
341
            } else {
342
                expect(instance.name).toEqual(Klass.prototype.name);
343
                expect(instance.message).toEqual(Klass.prototype.message);
344
            }
345
            expect(instance.code).toBeNull();
346
        }, (instance, even1, even2) => {
347
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
348
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
349
            expect(instance.code).toBeNull();
350
        }, (instance, even1, even2, even3) => {
351
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
352
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
353
            expect(instance.code).toEqual(even3 ? code : null);
354
        }, false);
355
    });
356
357
    // :: MEMBER METHODS
358
359
    it("#toString()", () => {
360
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
361
            let exp;
362
            if (even) {
363
                exp = name + ": " + Klass.prototype.message + '.';
364
            } else {
365
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
366
            }
367
            expect(instance.toString()).toEqual(exp);
368
        }, (instance, even1, even2) => {
369
            let exp;
370
            exp  = (even1 ? name : Klass.prototype.name) + ':';
371
            exp += ' ' + (even2 ? message : Klass.prototype.message) + '.';
372
            expect(instance.toString()).toEqual(exp);
373
        }, (instance, even1, even2, even3) => {
374
            let exp;
375
            exp  = (even1 ? name : Klass.prototype.name);
376
            exp += (even3 ? " (0x" + code.toString(16) + "):" : ':') + ' ';
377
            exp += (even2 ? message : Klass.prototype.message) + '.';
378
            expect(instance.toString()).toEqual(exp);
379
        }, false);
380
    });
381
382
    it("#native()", () => {
383
        instanceDefinedOrNull(Klass, name, message, code, (instance) => {
384
            const exp = Klass.prototype.message;
385
            expect(instance.native()).toEqual(new Error(exp));
386
        }, (instance, even1, even2) => {
387
            const exp = (even2 ? message : Klass.prototype.message);
388
            expect(instance.native()).toEqual(new Error(exp));
389
        }, (instance, even1, even2) => {
390
            const exp = (even2 ? message : Klass.prototype.message);
391
            expect(instance.native()).toEqual(new Error(exp));
392
        }, false);
393
    });
394
395
}